package framework.crypto;

import framework.config.AESCryptoConfig;
import framework.exceptions.ConfigurationException;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Date;

/**
 * AES实现
 */
public class AESCryptoImpl implements AESCrypto {

    private static final int GCM_LENGTH = 128;

    /**
     * 加密算法
     */
    @Getter
    private final String algorithm;

    /**
     * 加密解密算法/加密模式/填充方式
     */
    @Getter
//    private String cipherAlgorithm = "AES/CBC/PKCS5Padding";
    private final String cipherAlgorithm;

    /**
     * 密钥
     */
    @Getter
    private final String secretKey;
    private final byte[] secretData;

    // 随机数生成器
    private final SecureRandom secureRandom = new SecureRandom();
    private final int ivLength;

    @SneakyThrows
    public AESCryptoImpl(AESCryptoConfig config) {
        if (StringUtils.isBlank(config.getAlgorithm()))
            throw new ConfigurationException("Not config algorithm");
        if (StringUtils.isBlank(config.getCipherAlgorithm()))
            throw new ConfigurationException("Not config cipher algorithm");
        if (StringUtils.isBlank(config.getSecretKey()))
            throw new ConfigurationException("Not config secret key");
        //
        this.algorithm = config.getAlgorithm();
        this.cipherAlgorithm = config.getCipherAlgorithm();
        this.secretKey = config.getSecretKey();
        this.secretData = config.getSecretKey().getBytes(StandardCharsets.UTF_8);
        //
        SecretKey secretKey = new SecretKeySpec(this.secretData, this.algorithm);
        Cipher cipher = Cipher.getInstance(this.cipherAlgorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, this.secureRandom);
        this.ivLength = cipher.getIV().length;
    }

    public AESCryptoImpl(String secretKey) {
        this(new AESCryptoConfig(secretKey));
    }

    @SneakyThrows
    public Cipher getEncodeCipher() {
        SecretKey secretKey = new SecretKeySpec(this.secretData, this.algorithm);
        Cipher cipher = Cipher.getInstance(this.cipherAlgorithm);
        if ("GCM".equals(cipher.getParameters().getAlgorithm())) {
            byte[] ivBytes = new byte[this.ivLength];
            this.secureRandom.nextBytes(ivBytes);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(GCM_LENGTH, ivBytes));
        } else {
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, this.secureRandom);
        }
        return cipher;
    }

    @SneakyThrows
    public Cipher getDecodeCipher(byte[] encryptedData) {
        SecretKey secretKey = new SecretKeySpec(this.secretData, this.algorithm);
        Cipher cipher = Cipher.getInstance(this.cipherAlgorithm);
        //
        byte[] iv = new byte[this.ivLength];
        System.arraycopy(encryptedData, 0, iv, 0, iv.length);

        if ("GCM".equals(cipher.getParameters().getAlgorithm())) {
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(GCM_LENGTH, iv));
        } else {
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
        }
        //
        return cipher;
    }

    public static void main(String[] args) {

        ByteBuffer buffer;
        String encrypt, decrypt;
        String secretKey;
        secretKey = "6D9810A918FD43D0B180D89E4D195410";
//        secretKey = secretKey.substring(0, secretKey.length() / 2);
        AESCrypto crypto = new AESCryptoImpl(secretKey);

        String password = "1234567890";

        //
        encrypt = crypto.encodeHex(password);
        System.out.println("encrypt: " + encrypt);
        decrypt = crypto.decodeHex(encrypt);
        System.out.println("decrypt: " + decrypt);

        //
        encrypt = crypto.encodeHex(password);
        System.out.println("encrypt: " + encrypt);
        decrypt = crypto.decodeHex(encrypt);
        System.out.println("decrypt: " + decrypt);

        System.out.println("======================");

        encrypt = crypto.encodeBase64(password);
        System.out.println("encrypt: " + encrypt);
        decrypt = crypto.decodeBase64AsString(encrypt);
        System.out.println("decrypt: " + decrypt);

        encrypt = crypto.encodeBase64(password);
        System.out.println("encrypt: " + encrypt);
        decrypt = crypto.decodeBase64AsString(encrypt);
        System.out.println("decrypt: " + decrypt);

        System.out.println("======================");

        encrypt = crypto.encodeUrlBase64(password);
        System.out.println("encrypt: " + encrypt);
        decrypt = crypto.decodeUrlBase64AsString(encrypt);
        System.out.println("decrypt: " + decrypt);

        encrypt = crypto.encodeUrlBase64(password);
        System.out.println("encrypt: " + encrypt);
        decrypt = crypto.decodeUrlBase64AsString(encrypt);
        System.out.println("decrypt: " + decrypt);

        System.out.println("======================");

        //
        Long l1 = 123456789L;
        Long l2 = new Date().getTime() / 1000;
        byte[] b1 = ByteBuffer.allocate(Long.BYTES * 4)
                .putLong(0, l1)
                .putLong(8, l2)
                .array();

        encrypt = crypto.encodeHex(b1);
        System.out.println("l1: " + l1);
        System.out.println("l2: " + l2);
        System.out.println("encrypt: " + encrypt + " ,len: " + encrypt.length());

        b1 = crypto.decode(encrypt);
        buffer = ByteBuffer.wrap(b1);
        l1 = buffer.getLong(0);
        l2 = buffer.getLong(8);
        System.out.println("l1: " + l1);
        System.out.println("l2: " + l2);

        System.out.println("======================");

        encrypt = crypto.encodeUrlBase64(b1);
        System.out.println("l1: " + l1);
        System.out.println("l2: " + l2);
        System.out.println("encrypt: " + encrypt + " ,len: " + encrypt.length());

        b1 = crypto.decodeUrlBase64(encrypt);
        buffer = ByteBuffer.wrap(b1);
        l1 = buffer.getLong(0);
        l2 = buffer.getLong(8);
        System.out.println("l1: " + l1);
        System.out.println("l2: " + l2);


    }

}
